package elms

import (
	"github.com/containous/mux"
	"net/http"
	"strings"

	"github.com/traefik/traefik/log"
	"github.com/traefik/traefik/types"
	"github.com/urfave/negroni"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"strconv"
)

// Authenticator is a middleware that provides HTTP basic and digest authentication
type Authenticator struct {
	handler negroni.Handler
	users   map[string]string
	scopes  []string
}

var (
	paymentRequired = true
	router          *mux.Router
)

func init() {
	router = mux.NewRouter()
	router.PathPrefix("/oauth2/")
	router.PathPrefix("/api/elms/admin/")
}

// NewAuthenticator builds a new Autenticator given a config
func NewAuthenticator(authConfig *types.OAuth2) *Authenticator {
	authenticator := Authenticator{}
	authenticator.scopes = authConfig.Scopes
	authenticator.handler = negroni.HandlerFunc(authenticator.oAuth2Handler)
	return &authenticator
}

func (a *Authenticator) oAuth2Handler(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	tp := a.fetchAccessToken(r)
	if tp == "" {
		TokenErrorHandler(w, r)
	} else {
		conn := session.Copy()
		defer conn.Close()

		token := NewToken()
		err := conn.DB("ABCDE_db").C("oauth2.token").Find(bson.M{
			"token": tp,
		}).One(&token)

		if err == mgo.ErrNotFound || token.Expired() {
			TokenErrorHandler(w, r)
		} else {
			// scope could be "1", "id:1", "admin", "orgadmin", "admin,id:1"
			for _, scope := range a.scopes {
				for _, s := range strings.Split(scope, ",") {
					if !a.authorize(token, s) {
						log.Debugf("Need privilege %v to request %v", s, r.URL.RequestURI())
						ErrorHandler(w, r, NewError(PermisstonDenied))
						return
					}
				}
			}

			if token.Uid != nil {
				user := new(User)
				userColl := conn.DB(DbName(*token.Oid)).C("users")
				if err := userColl.FindId(token.Uid).Select(bson.M{"groupIds": 1}).One(&user); err == nil {
					if len(user.GroupIds) > 0 {
						ids := make([]string, len(user.GroupIds))
						for i, id := range user.GroupIds {
							ids[i] = id.Hex()
						}
						token.Acls = ids
					}
				}
			}

			token.ForwardHeaders(r)

			next.ServeHTTP(w, r)
			//a.paymentHandler(token, w, r, next)
		}
	}
}

func (a *Authenticator) paymentHandler(token *Token, w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if token.Uid != nil && token.RoleType > 50 && paymentRequired {
		if !router.Match(r, &mux.RouteMatch{}) {
			ErrorHandler(w, r, NewError(PaymentRequired))
			return
		}
	}
	next.ServeHTTP(w, r)
}

func (a *Authenticator) authorize(token *Token, scope string) bool {
	if i, err := strconv.Atoi(scope); err == nil {
		// old usage, scope is permission id.
		return token.Privileges.Authorize(i)
	} else if scope == "admin" {
		// need global admin role
		return token.Oid == nil || token.RoleType < 51
	} else if scope == "orgadmin" {
		// need org admin role
		return token.Oid == nil || token.RoleType < 52
	} else if strings.HasPrefix(scope, "id:") {
		id := strings.TrimPrefix(scope, "id:")
		if i, err := strconv.Atoi(id); err == nil {
			return token.Privileges.Authorize(i)
		}
	} else if scope == "" {
		return true
	}

	log.Warnf("Unknown scope %v, pass the request by default.", scope)
	return true
}

func (a *Authenticator) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	a.handler.ServeHTTP(rw, r, next)
}

func (a *Authenticator) fetchAccessToken(r *http.Request) (token string) {
	params := r.URL.Query()
	token = params.Get("access_token")
	if token == "" {
		token = params.Get("server_token")
	}

	if token == "" {
		token = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer")
		token = strings.TrimPrefix(token, "bearer")
		token = strings.TrimSpace(token)
	}

	if token == "" {
		token = strings.TrimPrefix(r.Header.Get("authorization"), "Bearer")
		token = strings.TrimPrefix(token, "bearer")
		token = strings.TrimSpace(token)
	}

	return
}
